msg_tool\scripts\silky/
mes.rs

1use super::disasm::*;
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use anyhow::Result;
7use std::cell::RefCell;
8use std::io::Write;
9use unicode_segmentation::UnicodeSegmentation;
10
11#[derive(Debug)]
12/// Sliky mes script builder
13pub struct MesBuilder {}
14
15impl MesBuilder {
16    /// Create a new Sliky mes script builder
17    pub fn new() -> Self {
18        Self {}
19    }
20}
21
22impl ScriptBuilder for MesBuilder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Cp932
25    }
26
27    fn build_script(
28        &self,
29        buf: Vec<u8>,
30        _filename: &str,
31        encoding: Encoding,
32        _archive_encoding: Encoding,
33        config: &ExtraConfig,
34        _archive: Option<&Box<dyn Script>>,
35    ) -> Result<Box<dyn Script>> {
36        Ok(Box::new(Mes::new(buf, encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["mes"]
41    }
42
43    fn script_type(&self) -> &'static ScriptType {
44        &ScriptType::Silky
45    }
46}
47
48struct TextParser<'a> {
49    data: Vec<&'a str>,
50    typ: SlikyStringType,
51    opcodes: &'static Opcodes,
52    encoding: Encoding,
53    pos: usize,
54}
55
56impl<'a> TextParser<'a> {
57    fn new(
58        s: &'a str,
59        typ: SlikyStringType,
60        opcodes: &'static Opcodes,
61        encoding: Encoding,
62    ) -> Self {
63        let data = s.graphemes(true).collect();
64        Self {
65            data,
66            typ,
67            opcodes,
68            encoding,
69            pos: 0,
70        }
71    }
72
73    fn parse(mut self) -> Result<Vec<u8>> {
74        match self.typ {
75            SlikyStringType::Internal => Err(anyhow::anyhow!(
76                "Internal strings cannot be parsed from text."
77            )),
78            SlikyStringType::Name => {
79                let mut m = MemWriter::new();
80                m.write_u8(self.opcodes.push_string)?;
81                let s = encode_string(self.encoding, &self.data.join(""), false)?;
82                m.write_all(&s)?;
83                m.write_u8(0)?;
84                Ok(m.into_inner())
85            }
86            SlikyStringType::Message => {
87                let mut m = MemWriter::new();
88                let mut in_ruby = false;
89                let mut in_normal_text = false;
90                while let Some(c) = self.next() {
91                    match c {
92                        "[" => {
93                            if in_ruby {
94                                return Err(anyhow::anyhow!("Nested ruby tags are not allowed."));
95                            }
96                            if in_normal_text {
97                                m.write_u8(0)?;
98                                in_normal_text = false;
99                            }
100                            in_ruby = true;
101                            m.write_u8(self.opcodes.escape_sequence)?;
102                            m.write_u8(1)?; // ruby start
103                            m.write_u8(self.opcodes.message2)?;
104                        }
105                        "]" => {
106                            if !in_ruby {
107                                return Err(anyhow::anyhow!("Unmatched closing ruby tag."));
108                            }
109                            in_ruby = false;
110                            m.write_u8(0)?;
111                            m.write_u8(self.opcodes.r#yield)?;
112                        }
113                        "\n" => {
114                            if in_ruby {
115                                return Err(anyhow::anyhow!("Newline inside ruby is not allowed."));
116                            }
117                            if in_normal_text {
118                                m.write_u8(0)?;
119                                in_normal_text = false;
120                            }
121                            m.write_u8(self.opcodes.escape_sequence)?;
122                            m.write_u8(0)?; // new line
123                        }
124                        _ => {
125                            if !in_ruby && !in_normal_text {
126                                in_normal_text = true;
127                                m.write_u8(self.opcodes.message2)?;
128                            }
129                            let s = encode_string(self.encoding, c, false)?;
130                            m.write_all(&s)?;
131                        }
132                    }
133                }
134                if in_ruby {
135                    m.write_u8(0)?;
136                    m.write_u8(self.opcodes.r#yield)?;
137                }
138                if in_normal_text {
139                    m.write_u8(0)?;
140                }
141                Ok(m.into_inner())
142            }
143        }
144    }
145
146    fn next(&mut self) -> Option<&'a str> {
147        if self.pos < self.data.len() {
148            let c = self.data[self.pos];
149            self.pos += 1;
150            Some(c)
151        } else {
152            None
153        }
154    }
155}
156
157#[derive(Debug)]
158pub struct Mes {
159    disasm: RefCell<Box<dyn Disasm>>,
160    encoding: Encoding,
161    texts: Vec<SlikyString>,
162}
163
164impl Mes {
165    pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
166        let reader = MemReader::new(buf);
167        let num_message = reader.cpeek_u32()?;
168        let code_offset = 4 + num_message as u64 * 4;
169        let first_line_offset = reader.cpeek_u32_at(4)? as u64 + code_offset;
170        let mut disasm: Box<dyn Disasm> = if reader.cpeek_u8_at(first_line_offset)? == 0x19
171            && reader.cpeek_u32_at(first_line_offset + 1)? == 0
172        {
173            Box::new(Ai6WinDisasm::new(reader)?)
174        } else {
175            Box::new(PlusDisasm::new(reader)?)
176        };
177        disasm.read_header()?;
178        let texts = disasm.read_code()?;
179        Ok(Self {
180            disasm: RefCell::new(disasm),
181            encoding,
182            texts,
183        })
184    }
185
186    fn code_to_text(&self, str: &SlikyString) -> Result<String> {
187        let mut disasm = self.disasm.try_borrow_mut()?;
188        let mut result = String::new();
189        disasm.stream_mut().pos = str.start as usize;
190        let end = str.start as usize + str.len as usize;
191        let opcodes = disasm.opcodes();
192        while disasm.stream().pos < end {
193            let (opcode, operands) = disasm.read_instruction()?;
194            if opcode == opcodes.push_string
195                || (opcode == opcodes.message1 && !opcodes.is_message1_obfuscated)
196                || opcode == opcodes.message2
197            {
198                if let Some(Obj::Str(s)) = operands.get(0) {
199                    let s = disasm.stream().cpeek_fstring_at(
200                        s.start,
201                        s.len as usize,
202                        self.encoding,
203                        true,
204                    )?;
205                    result.push_str(&s);
206                }
207            } else if opcode == opcodes.message1 && opcodes.is_message1_obfuscated {
208                if let Some(Obj::Str(s)) = operands.get(0) {
209                    let mut deobfuscated = vec![0u8; (s.len as usize - 1) * 2];
210                    let mut input_idx = 0;
211                    let mut output_idx = 0;
212                    let tlen = s.len - 1;
213                    while input_idx < tlen {
214                        let b = disasm.stream().cpeek_u8_at(s.start + input_idx)?;
215                        input_idx += 1;
216                        if matches!(b, 0x81..0xA0 | 0xE0..0xF0) {
217                            deobfuscated[output_idx] = b;
218                            output_idx += 1;
219                            deobfuscated[output_idx] =
220                                disasm.stream().cpeek_u8_at(s.start + input_idx)?;
221                            input_idx += 1;
222                            output_idx += 1;
223                        } else {
224                            let c = b as i32 - 0x7D62;
225                            deobfuscated[output_idx] = (c >> 8) as u8;
226                            output_idx += 1;
227                            deobfuscated[output_idx] = (c & 0xFF) as u8;
228                            output_idx += 1;
229                        }
230                    }
231                    deobfuscated.truncate(output_idx);
232                    let s = decode_to_string(self.encoding, &deobfuscated, true)?;
233                    result.push_str(&s);
234                }
235            } else if opcode == opcodes.escape_sequence {
236                if let Some(Obj::Byte(e)) = operands.get(0) {
237                    match e {
238                        // new line
239                        0 => result.push('\n'),
240                        // ruby
241                        1 => result.push_str("["),
242                        _ => {
243                            return Err(anyhow::anyhow!("Unknown escape sequence: {}", e));
244                        }
245                    }
246                }
247            } else if opcode == opcodes.r#yield {
248                result.push_str("]");
249            }
250        }
251        Ok(result)
252    }
253}
254
255impl Script for Mes {
256    fn default_output_script_type(&self) -> OutputScriptType {
257        OutputScriptType::Json
258    }
259
260    fn default_format_type(&self) -> FormatOptions {
261        FormatOptions::None
262    }
263
264    fn extract_messages(&self) -> Result<Vec<Message>> {
265        let mut messages = Vec::new();
266        let mut name = None;
267        for t in self.texts.iter() {
268            match t.typ {
269                SlikyStringType::Internal => {}
270                SlikyStringType::Name => {
271                    name = Some(self.code_to_text(t)?);
272                }
273                SlikyStringType::Message => {
274                    let message = self.code_to_text(t)?;
275                    messages.push(Message {
276                        name: name.take(),
277                        message,
278                    });
279                }
280            }
281        }
282        Ok(messages)
283    }
284
285    fn import_messages<'a>(
286        &'a self,
287        messages: Vec<Message>,
288        file: Box<dyn WriteSeek + 'a>,
289        _filename: &str,
290        encoding: Encoding,
291        replacement: Option<&'a ReplacementTable>,
292    ) -> Result<()> {
293        let opcodes = self.disasm.try_borrow()?.opcodes();
294        let mut inp = self.disasm.try_borrow()?.stream().clone();
295        inp.pos = 0;
296        let mut patcher = BinaryPatcher::new(inp.to_ref(), file, |add| Ok(add), |add| Ok(add))?;
297        let mut mess = messages.iter();
298        let mut mes = mess.next();
299        for text in &self.texts {
300            patcher.copy_up_to(text.start)?;
301            match text.typ {
302                // Ignore internal strings
303                SlikyStringType::Internal => {}
304                SlikyStringType::Name => {
305                    let m = match mes {
306                        Some(m) => m,
307                        None => {
308                            return Err(anyhow::anyhow!("Not enough messages"));
309                        }
310                    };
311                    let mut name = match &m.name {
312                        Some(n) => n.to_string(),
313                        None => {
314                            return Err(anyhow::anyhow!("Message name is missing"));
315                        }
316                    };
317                    if let Some(repl) = replacement {
318                        for (k, v) in &repl.map {
319                            name = name.replace(k, v);
320                        }
321                    }
322                    let data =
323                        TextParser::new(&name, SlikyStringType::Name, opcodes, encoding).parse()?;
324                    patcher.replace_bytes(text.len, &data)?;
325                }
326                SlikyStringType::Message => {
327                    let m = match mes {
328                        Some(m) => m,
329                        None => {
330                            return Err(anyhow::anyhow!("Not enough messages"));
331                        }
332                    };
333                    let mut message = m.message.to_string();
334                    if let Some(repl) = replacement {
335                        for (k, v) in &repl.map {
336                            message = message.replace(k, v);
337                        }
338                    }
339                    let data =
340                        TextParser::new(&message, SlikyStringType::Message, opcodes, encoding)
341                            .parse()?;
342                    patcher.replace_bytes(text.len, &data)?;
343                    mes = mess.next();
344                }
345            }
346        }
347        if mes.is_some() || mess.next().is_some() {
348            return Err(anyhow::anyhow!("Too many messages"));
349        }
350        patcher.copy_up_to(inp.data.len() as u64)?;
351        let code_offset = self.disasm.try_borrow()?.code_offset();
352        for &address_offset in self.disasm.try_borrow()?.little_endian_addresses() {
353            let orig_address = inp.cpeek_u32_at(address_offset as u64)? as u64;
354            let orig_offset = orig_address + code_offset as u64;
355            let new_offset = patcher.map_offset(orig_offset)?;
356            let new_address = new_offset - code_offset as u64;
357            patcher.patch_u32(address_offset as u64, new_address as u32)?;
358        }
359        for &address_offset in self.disasm.try_borrow()?.big_endian_addresses() {
360            let orig_address = inp.cpeek_u32_be_at(address_offset as u64)? as u64;
361            let orig_offset = orig_address + code_offset as u64;
362            let new_offset = patcher.map_offset(orig_offset)?;
363            let new_address = new_offset - code_offset as u64;
364            patcher.patch_u32_be(address_offset as u64, new_address as u32)?;
365        }
366        Ok(())
367    }
368}
369
370#[test]
371fn test_text_parser() {
372    let opcodes = &PLUS_OPCODES;
373    let parser = TextParser::new(
374        "Hello, [world]s\nThis is a test.",
375        SlikyStringType::Message,
376        opcodes,
377        Encoding::Utf8,
378    );
379    let data = parser.parse().unwrap();
380    assert_eq!(
381        data,
382        vec![
383            opcodes.message2,
384            b'H',
385            b'e',
386            b'l',
387            b'l',
388            b'o',
389            b',',
390            b' ',
391            0,
392            opcodes.escape_sequence,
393            1,
394            opcodes.message2,
395            b'w',
396            b'o',
397            b'r',
398            b'l',
399            b'd',
400            0,
401            opcodes.r#yield,
402            opcodes.message2,
403            b's',
404            0,
405            opcodes.escape_sequence,
406            0,
407            opcodes.message2,
408            b'T',
409            b'h',
410            b'i',
411            b's',
412            b' ',
413            b'i',
414            b's',
415            b' ',
416            b'a',
417            b' ',
418            b't',
419            b'e',
420            b's',
421            b't',
422            b'.',
423            0
424        ]
425    );
426}